package sudoku

// CheckStatus reprents sudoku check state
type CheckStatus int

const (
	// SValid means sudoku finished and all is valid
	SValid CheckStatus = iota
	// SValidNotFinished means sudoku is valid, but remain empty block
	SValidNotFinished
	// SInvalid means sudoku is invalid
	SInvalid
)

// CheckResType is the result type of checkValid
type CheckResType struct {
	// Status is checkstatus
	Status CheckStatus
	// ConflictElem reprents those elements in conflict
	ConflictElem GridType
	// ValidElem reprents those valid elements
	ValidElem GridType
	// The Size to be filled (empty in original puzzle)
	ToFilledSize int
}

// IValidChecker is to be implemented by SudokuValidChecker
type IValidChecker interface {
	// Start to check with answers, return itself
	Check(ansGrid GridType) IValidChecker
	// Init with game, can be call multiple times
	Init(game GridType) IValidChecker
	// Reset clear all inner state
	Reset() IValidChecker
	// GetResult gets final result of check
	GetResult() CheckResType
}

// AnswerCheckService saves puzzle, and check user answers
type AnswerCheckService struct {
	gameMap [][]int
}

// Init with game, can be call multiple times
func (c *AnswerCheckService) Init(game GridType) *AnswerCheckService {
	if game == nil {
		panic("AnswerCheckService: gameMap is nil")
	}
	// reset first
	// init with game map
	c.gameMap = make([][]int, 9)
	for i := range c.gameMap {
		c.gameMap[i] = make([]int, 9)
	}
	fillWithGrid(c.gameMap, game)
	return c
}

// Check is Pure function (if readonly to AnswerCheckService)
func (c *AnswerCheckService) Check(answer GridType) CheckResType {
	checker := NewValidChecker(c.gameMap)
	return checker.Check(answer).GetResult()
}

// NewAnswerCheckService new answer check service
func NewAnswerCheckService(game GridType) *AnswerCheckService {
	s := &AnswerCheckService{}
	s.Init(game)
	return s
}

// ValidChecker will check validtion of sudoku
// !! Each function in ValidChecker should only
// !! Trust the gameMap (take the priority to first)
// !! The User Input ansMap should be un-trustable
type ValidChecker struct {
	// save the map of a game
	gameMap [][]int
	// count for non-zero value on gameMap
	gameMapSize int

	// temp states of checker (will be reset for each answer)
	result     CheckResType
	hasChecked bool
	// useful in check process
	answerMap [][]int
	// is the answer have zero? (not finished)
	isFinished bool
	// hash-map for unique value check
	valSet [10]int
}

// NewValidChecker create checker with initial game map
func NewValidChecker(game GridType) *ValidChecker {
	c := &ValidChecker{}
	// !!: find a more elegant way
	return c.Init(game).(*ValidChecker)
}

// subCheck a value, require i, j and make sure they
// are in the same unit(row, col, block)
func (c *ValidChecker) subCheck(i, j int) {
	// get value from game or answer
	var val int
	// use gameMap first to avoid value be
	// overrided by user's answer
	if c.gameMap[i][j] != 0 {
		val = c.gameMap[i][j]
	} else {
		val = c.answerMap[i][j]
	}
	if val == 0 {
		// sudoku isn't finished
		c.isFinished = false
		// ignore if not set
		return
	}

	// unique value
	if c.valSet[val] == -1 {
		// set to hash (i, j) -> 10 * i + j
		c.valSet[val] = 10*i + j
	} else {
		// have conflicts
		// only insert to conflict when it is answer
		if c.gameMap[i][j] == 0 {
			c.result.ConflictElem.Set(i, j, val)
		}
		// insert old_i, old_j, load from hash
		oldI, oldJ := c.valSet[val]/10, c.valSet[val]%10
		if c.gameMap[oldI][oldJ] == 0 {
			c.result.ConflictElem.Set(oldI, oldJ, val)
		}
		// set status to invalid
		c.result.Status = SInvalid
	}
}

// Check Start to check with answers, return itself
func (c *ValidChecker) Check(ansGrid GridType) IValidChecker {
	if ansGrid == nil {
		panic("sudokuValidChecker: ansGrid is nil")
	}
	// assert to caller
	if c.gameMapSize == 0 {
		panic("sudokuValidChecker: check before init")
	}

	c.Reset()
	// constrcuct answerMap and posGen
	fillWithGrid(c.answerMap, ansGrid)
	// generate position info
	var posGen positionGenerator

	// check in row, col, block
	stateToCheck := []positionGeneratorState{gRow, gCol, gBlock}
	for _, state := range stateToCheck {
		posGen.SetState(state)
		for {
			i, j, nf, err := posGen.Next()
			if err != nil {
				break
			}
			// change unit now
			if nf {
				fillVal(&c.valSet, -1)
			}
			c.subCheck(i, j)
		}
	}

	// construct ValidElem, iter original answer
	for i, row := range ansGrid {
		for j, val := range row {
			// gameMap[i][j] should not exist
			if ansGrid[i][j] != 0 && c.gameMap[i][j] == 0 {
				ce := c.result.ConflictElem[i][j]
				// not in conflict grid, add to valid grid
				if ce == 0 && c.gameMap[i][j] == 0 {
					c.result.ValidElem.Set(i, j, val)
				}
			}
		}
	}

	// set status according to finish
	if !c.isFinished && c.result.Status == SValid {
		c.result.Status = SValidNotFinished
	}
	// hasCheck
	c.hasChecked = true
	c.result.ToFilledSize = c.gameMapSize
	return c
}

// Init with game, can be call multiple times
func (c *ValidChecker) Init(game GridType) IValidChecker {
	if game == nil {
		panic("sudokuValidChecker: gameMap is nil")
	}
	// reset first
	c.Reset()
	// init with game map
	c.gameMap = game
	c.gameMapSize = game.Size()
	return c
}

// Reset clear all inner state
func (c *ValidChecker) Reset() IValidChecker {
	// Reset all
	c.result = CheckResType{
		Status:       SValid,
		ConflictElem: MakeGridType(),
		ValidElem:    MakeGridType(),
		ToFilledSize: 0,
	}
	c.hasChecked = false
	// set all array to zero
	c.answerMap = makeSudokuGrid(9)
	c.isFinished = true
	fillVal(&c.valSet, -1)

	return c
}

// GetResult gets final result of check
func (c *ValidChecker) GetResult() CheckResType {
	// assert to caller
	if !c.hasChecked {
		panic("sudokuCheckValid: call GetResult before check")
	}
	return c.result
}
